package com.qqbot.utils;

import com.qqbot.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 付亚全
 *
 * @author 付亚全
 */
@Slf4j
@Component
public class RedisUtil {

    public static final Long TOKEN_EXPIRE_TIME_IN_MINUTES = 30L;

    public static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MINUTES;

    @Resource
    private StringRedisTemplate autoStringRedisTemplate;

    private static StringRedisTemplate stringRedisTemplate;

    private static final Integer REDIS_KEY_CODE = 10001;
    private static final String REDIS_KEY_MESSAGE = "redis key 不能够为空";
    private static final Integer SAVE_DATA_OBJECT_CODE = 10002;
    private static final String SAVE_DATA_OBJECT_MESSAGE = "存储对象不能够为空";
    private static final Integer REDIS_KEY_IS_EXISTS_CODE = 10003;
    private static final String REDIS_KEY_IS_EXISTS_MESSAGE = "redis key 不存在";

    @PostConstruct
    public void init() {
        log.info("初始化redis模板");
        stringRedisTemplate = autoStringRedisTemplate;
    }

    /**
     * 往zset集合中添加元素，如果zset不存在则先添加zset再添加元素，如果元素存在则覆盖
     *
     * @param key
     * @param value
     * @param score
     * @param expire
     */
    public static void zSetAdd(String key, String value, Double score, Long expire) {
        BoundZSetOperations<String, String> zSet = stringRedisTemplate.boundZSetOps(key);
        zSet.expire(expire, TimeUnit.SECONDS);
        zSet.add(value, score);
    }

    /**
     * 增加对应zset集合中某个元素的score值
     *
     * @param key
     * @param value
     * @param score
     */
    public static void incrementScore(String key, String value, Double score) {
        BoundZSetOperations<String, String> zSet = stringRedisTemplate.boundZSetOps(key);
        zSet.expire(DateUtil.getTodayLeftSeconds(new Date()), TimeUnit.SECONDS);
        zSet.incrementScore(value, score);
    }

    public static Set<String> reverseRange(String key, int start, int end) {
        BoundZSetOperations<String, String> zSet = stringRedisTemplate.boundZSetOps(key);
        Set<String> set = zSet.reverseRange(start, end);
        return set;
    }

    /**
     * 将数据存入redis--不管存在还是不存在原数据，直接保存
     *
     * @param key
     * @param content
     * @throws BusinessException
     */
    public static void set(String key, String content) throws BusinessException {
        checkRedisKeyIsNullAndValNotNull(key, content);
        if (StringUtils.isBlank(content)) {
            throw new BusinessException(SAVE_DATA_OBJECT_CODE, SAVE_DATA_OBJECT_MESSAGE);
        }
        //存入redis
        ValueOperations<String, String> valueOperation = stringRedisTemplate.opsForValue();
        valueOperation.set(key, content);
        log.info("save redis success");
    }

    /**
     * 将数据存入redis--不管存在还是不存在原数据，直接保存
     *
     * @param key
     * @param content     json字符串
     * @param minutesTime 过期时间
     * @throws BusinessException
     */
    public static void set(String key, String content, Long minutesTime, TimeUnit timeUnit) throws BusinessException {
        checkRedisKeyIsNullAndValNotNull(key, content);
        if (StringUtils.isBlank(content)) {
            throw new BusinessException(SAVE_DATA_OBJECT_CODE, SAVE_DATA_OBJECT_MESSAGE);
        }
        //存入redis
        ValueOperations<String, String> valueOperation = stringRedisTemplate.opsForValue();
        valueOperation.set(key, content, minutesTime == null ? TOKEN_EXPIRE_TIME_IN_MINUTES : minutesTime, timeUnit == null ? DEFAULT_TIME_UNIT : timeUnit);
        log.info("save redis success");
    }

    /**
     * 根据key删除redis信息
     *
     * @param key
     */
    public static void deleteByKey(String key) throws BusinessException {
        if (!judgeKeyIsExists(key)) {
            return;
        }
        stringRedisTemplate.delete(key);
        log.info("delete success");
    }

    /**
     * 获取redis数据，字符串格式
     *
     * @param key
     * @return
     * @throws BusinessException
     */
    public static String getByKey(String key) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        //获取redis用户数据
        ValueOperations<String, String> valueOperation = stringRedisTemplate.opsForValue();
        String result = valueOperation.get(key);
        log.info("query success");
        return result;
    }

    /**
     * 获取redis数据，将数据转为对象格式
     *
     * @param key
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getByKeyToEntity(String key, Class<T> clazz) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        //获取redis用户数据
        ValueOperations<String, String> valueOperation = stringRedisTemplate.opsForValue();
        T obj = JsonUtil.jsonToEntity(valueOperation.get(key), clazz);
        log.info("query and convert entity success");
        return obj;
    }

    /**
     * 获取redis数据，转换为list对象
     *
     * @param key
     * @param clazz
     * @param <T>
     * @return
     * @throws BusinessException
     */
    public static <T> List<T> getByKeyToList(String key, Class<T> clazz) throws BusinessException {
        if (judgeKeyIsExists(key)) {
            return Collections.EMPTY_LIST;
        }
        //获取redis用户数据
        ValueOperations<String, String> valueOperation = stringRedisTemplate.opsForValue();
        List<T> list = JsonUtil.jsonToList(valueOperation.get(key), clazz);
        log.info("query and convert list success");
        return list;
    }

    /**
     * 设置过期时间
     *
     * @param key
     * @param time
     * @param timeUnit
     */
    public static void expire(String key, Long time, TimeUnit timeUnit) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        stringRedisTemplate.expire(key, time, timeUnit);
    }

    /**
     * 获取过期时间
     *
     * @param key
     * @throws BusinessException
     */
    public static Long getExpire(String key) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        return stringRedisTemplate.getExpire(key);
    }

    /**
     * 获取过期时间，并转换为指定单位
     *
     * @param key
     * @param timeUnit
     * @throws BusinessException
     */
    public static Long getExpire(String key, TimeUnit timeUnit) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        return stringRedisTemplate.getExpire(key, timeUnit);
    }

    /**
     * 对key中的value进行操作
     *
     * @param key
     * @param val
     * @return
     * @throws BusinessException
     */
    public static Long increment(String key, long val) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        return stringRedisTemplate.boundValueOps(key).increment(val);
    }

    /**
     * 向集合中存入一个或多个数据
     *
     * @param key
     * @param strings
     * @throws BusinessException
     */
    public static Long add(String key, String... strings) throws BusinessException {
        checkRedisKeyIsNullAndValNotNull(key, strings);
        return stringRedisTemplate.opsForSet().add(key, strings);
    }

    /**
     * 删除集合中一个或多个元素
     *
     * @param key
     * @param strings
     * @return
     * @throws BusinessException
     */
    public static Long remove(String key, String... strings) throws BusinessException {
        checkRedisKeyIsNullAndIsExistsAndValNotNull(key, strings);
        return stringRedisTemplate.opsForSet().remove(key, strings);
    }

    /**
     * 保存HashMap数据
     *
     * @param key
     * @param mapKey
     * @param mapVal
     * @throws BusinessException
     */
    public static void put(String key, Object mapKey, Object mapVal) throws BusinessException {
        checkRedisKeyIsNullAndValNotNull(key, mapKey);
        stringRedisTemplate.opsForHash().put(key, mapKey, mapVal);
    }

    /**
     * 获取指定变量中的hashMap值。
     *
     * @param key
     * @return
     * @throws BusinessException
     */
    public static List<Object> values(String key) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        return stringRedisTemplate.opsForHash().values(key);
    }

    /**
     * 获取执行key存储的hash键值对
     *
     * @param key
     * @return
     * @throws BusinessException
     */
    public static Map<Object, Object> entries(String key) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        return stringRedisTemplate.opsForHash().entries(key);
    }

    /**
     * 获取存入的hashMap中某个指定键的值
     *
     * @param key
     * @param mapKey
     * @return
     * @throws BusinessException
     */
    public static Object getHashValueByMapKey(String key, Object mapKey) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        return stringRedisTemplate.opsForHash().get(key, mapKey);
    }

    /**
     * 判断hashMap中的键是否存在
     *
     * @param key
     * @param mapKey
     * @return
     * @throws BusinessException
     */
    public static boolean judgeHashMapKeyIsExists(String key, Object mapKey) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        return stringRedisTemplate.opsForHash().hasKey(key, mapKey);
    }

    /**
     * 获取hashMap中的所有键
     *
     * @param key
     * @return
     * @throws BusinessException
     */
    public static Set<Object> hashKeys(String key) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        return stringRedisTemplate.opsForHash().keys(key);
    }

    /**
     * 获取hashMap的长度、大小
     *
     * @param key
     * @return
     * @throws BusinessException
     */
    public static Long getHashMapSize(String key) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        return stringRedisTemplate.opsForHash().size(key);
    }

    /**
     * hashMap某个键下面的值进行操作，加、减
     *
     * @param key
     * @param mapKey
     * @param val
     * @return
     * @throws BusinessException
     */
    public static Long incrementHashMapValue(String key, Object mapKey, Long val) throws BusinessException {
        checkRedisKeyIsNullAndIsExistsAndValNotNull(key, mapKey);
        return stringRedisTemplate.opsForHash().increment(key, mapKey, val);
    }

    /**
     * hashMap某个键下面的值进行操作，加、减
     *
     * @param key
     * @param mapKey
     * @param val
     * @return
     * @throws BusinessException
     */
    public static Double incrementHashMapValue(String key, Object mapKey, Double val) throws BusinessException {
        checkRedisKeyIsNullAndIsExistsAndValNotNull(key, mapKey);
        return stringRedisTemplate.opsForHash().increment(key, mapKey, val);
    }

    /**
     * 批量获取map中的值，根据多个key
     *
     * @param key
     * @param mapKeyList
     * @return
     * @throws BusinessException
     */
    public static List<Object> multiGetHashMapValueByMapKeyList(String key, List<Object> mapKeyList) throws BusinessException {
        checkRedisKeyIsNullAndIsExistsAndValNotNull(key, mapKeyList);
        return stringRedisTemplate.opsForHash().multiGet(key, mapKeyList);
    }

    /**
     * 保存hashMap
     *
     * @param key
     * @param map
     * @throws BusinessException
     */
    public static void hashMapPutAll(String key, Map<Object, Object> map) throws BusinessException {
        checkRedisKeyIsNullAndValNotNull(key, map);
        stringRedisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 如果变量值存在，在变量中可以添加不存在的的键值对，如果变量不存在，则新增一个变量，同时将键值对添加到该变量。
     *
     * @param key
     * @param mapKey
     * @param mapVal
     * @throws BusinessException
     */
    public static void putIfAbsent(String key, Object mapKey, Object mapVal) throws BusinessException {
        checkRedisKeyIsNullAndIsExistsAndValNotNull(key, mapKey);
        stringRedisTemplate.opsForHash().putIfAbsent(key, mapKey, mapVal);
    }

    /**
     * 匹配获取键值对
     *
     * @param key
     * @param scanOptions
     * @return
     * @throws BusinessException
     */
    public static Cursor<Map.Entry<Object, Object>> scan(String key, ScanOptions scanOptions) throws BusinessException {
        checkRedisKeyIsNullAndIsExistsAndValNotNull(key, scanOptions);
        return stringRedisTemplate.opsForHash().scan(key, scanOptions);
    }

    /**
     * 批量删除键值对
     *
     * @param key
     * @param mapKeys
     * @return
     * @throws BusinessException
     */
    public static Long hashMapDelete(String key, String... mapKeys) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        return stringRedisTemplate.opsForHash().delete(key, mapKeys);
    }

    /**
     * 在集合左边添加元素值。
     *
     * @param key
     * @param val
     * @return
     * @throws BusinessException
     */
    public static Long leftPush(String key, String val) throws BusinessException {
        checkRedisKeyIsNullAndValNotNull(key, val);
        return stringRedisTemplate.opsForList().leftPush(key, val);
    }

    /**
     * 获取指定集合下标的数据
     *
     * @param key
     * @param index
     * @return
     * @throws BusinessException
     */
    public static String indexList(String key, Long index) throws BusinessException {
        checkRedisKeyIsNullAndIsExistsAndValNotNull(key, index);
        return stringRedisTemplate.opsForList().index(key, index);
    }

    /**
     * 获取集合指定开始和结束下标的数据
     *
     * @param key
     * @param startIndex
     * @param endIndex
     * @return
     * @throws BusinessException
     */
    public static List<String> rangeList(String key, Long startIndex, Long endIndex) throws BusinessException {
        checkRedisKeyIsNullAndIsExistsAndValNotNull(key, startIndex);
        checkRedisKeyIsNullAndIsExistsAndValNotNull(key, endIndex);
        return stringRedisTemplate.opsForList().range(key, startIndex, endIndex);
    }

    /**
     * 批量添加集合数据
     *
     * @param key
     * @param list
     * @return
     * @throws BusinessException
     */
    public static Long leftPushAll(String key, List<String> list) throws BusinessException {
        checkRedisKeyIsNullAndValNotNull(key, list);
        return stringRedisTemplate.opsForList().leftPushAll(key, list);
    }

    /**
     * 集合如果存在，添加元素
     *
     * @param key
     * @param val
     * @return
     * @throws BusinessException
     */
    public static Long leftPushIfPresent(String key, String val) throws BusinessException {
        checkRedisKeyIsNullAndIsExistsAndValNotNull(key, val);
        return stringRedisTemplate.opsForList().leftPushIfPresent(key, val);
    }

    /**
     * 集合最右边添加
     *
     * @param key
     * @param val
     * @return
     * @throws BusinessException
     */
    public static Long rightPush(String key, String val) throws BusinessException {
        checkRedisKeyIsNullAndValNotNull(key, val);
        return stringRedisTemplate.opsForList().rightPush(key, val);
    }

    /**
     * 集合最右边批量添加
     *
     * @param key
     * @param valList
     * @return
     * @throws BusinessException
     */
    public static Long rightPushAll(String key, List<String> valList) throws BusinessException {
        checkRedisKeyIsNullAndValNotNull(key, valList);
        return stringRedisTemplate.opsForList().rightPushAll(key, valList);
    }

    /**
     * 获取集合长度
     *
     * @param key
     * @return
     * @throws BusinessException
     */
    public static Long listSize(String key) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        return stringRedisTemplate.opsForList().size(key);
    }

    /**
     * 向已存在的集合中添加元素
     *
     * @param key
     * @param val
     * @return
     * @throws BusinessException
     */
    public static Long rightPushIfPresent(String key, String val) throws BusinessException {
        checkRedisKeyIsNullAndIsExistsAndValNotNull(key, val);
        return stringRedisTemplate.opsForList().rightPushIfPresent(key, val);
    }

    /**
     * 移除左边第一个元素
     *
     * @param key
     * @return
     * @throws BusinessException
     */
    public static String removeLeftPop(String key) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        return stringRedisTemplate.opsForList().leftPop(key);
    }

    /**
     * 移除右边元素
     *
     * @param key
     * @return
     * @throws BusinessException
     */
    public static String removeRightPop(String key) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        return stringRedisTemplate.opsForList().rightPop(key);
    }

    /**
     * 在集合中添加元素
     *
     * @param key
     * @param index
     * @param val
     * @throws BusinessException
     */
    public static void setInsert(String key, long index, String val) throws BusinessException {
        checkRedisKeyIsNullAndIsExistsAndValNotNull(key, val);
        stringRedisTemplate.opsForList().set(key, index, val);
    }

    /**
     * 从存储在键中的列表中删除等于值的元素的第一个计数事件。count> 0：删除等于从左到右移动的值的第一个元素；count< 0：删除等于从右到左移动的值的第一个元素；
     * count = 0：删除等于value的所有元素。
     *
     * @param key   redis key
     * @param count 删除标识：count>0,
     * @param val
     * @return
     * @throws BusinessException
     */
    public static Long remove(String key, long count, Object val) throws BusinessException {
        checkRedisKeyIsNullAndIsExistsAndValNotNull(key, val);
        return stringRedisTemplate.opsForList().remove(key, count, val);
    }

    /**
     * 截取集合
     *
     * @param key
     * @param startIndex
     * @param endIndex
     * @throws BusinessException
     */
    public static void trim(String key, long startIndex, long endIndex) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        stringRedisTemplate.opsForList().trim(key, startIndex, endIndex);
    }

    /**
     * 判断key是否存在
     *
     * @param key
     * @return
     */
    public static boolean judgeKeyIsExists(String key) throws BusinessException {
        if (StringUtils.isBlank(key)) {
            throw new BusinessException(REDIS_KEY_CODE, REDIS_KEY_MESSAGE);
        }
        return stringRedisTemplate.hasKey(key);
    }

    /**
     * 校验redis key是否为空，或者是否存在
     *
     * @param key
     * @throws BusinessException
     */
    public static void checkRedisKeyIsNullAndIsExists(String key) throws BusinessException {
        if (StringUtils.isBlank(key)) {
            throw new BusinessException(REDIS_KEY_CODE, REDIS_KEY_MESSAGE);
        }
        if (!judgeKeyIsExists(key)) {
            throw new BusinessException(REDIS_KEY_IS_EXISTS_CODE, REDIS_KEY_IS_EXISTS_MESSAGE);
        }
    }

    public static void checkRedisKeyIsNullAndIsExistsAndValNotNull(String key, Object val) throws BusinessException {
        checkRedisKeyIsNullAndIsExists(key);
        if (null == val) {
            throw new BusinessException(SAVE_DATA_OBJECT_CODE, SAVE_DATA_OBJECT_MESSAGE);
        }
    }

    public static void checkRedisKeyIsNullAndValNotNull(String key, Object val) throws BusinessException {
        if (StringUtils.isBlank(key)) {
            throw new BusinessException(REDIS_KEY_CODE, REDIS_KEY_MESSAGE);
        }
        if (null == val) {
            throw new BusinessException(SAVE_DATA_OBJECT_CODE, SAVE_DATA_OBJECT_MESSAGE);
        }
    }
}
